local class = reload("class")
local clist = reload("list")
local tvoid = reload("tvoid")
local super = reload("super.basic")
local cdkeys = reload("plugins.cdkeys")
local timer = reload("single.timer")
local clone = reload("table.opt.clone")
local clear = reload("table.opt.clear")
local midws = reload("global.midws")

---邮件状态
local eflgs = {
    init = 0, --创建
    read = 1, --已读
    recv = 2, --已领
    move = 3, --删除
}

---邮件数值字段
local numKeys = {
    uuid = true,
    from = true,
    toid = true,
    type = true,
    minlv = true,
    maxlv = true,
    ctime = true,
    dtime = true,
    state = true,
}


---离线邮件列表zsort
local function key_offline_zsort(cdkey)
    return string.format("email:zsort:%d:offline:%d", midws.rdb._info.zone, cdkey)
end

---离线邮件数据hash
local function key_offline_hash(cdkey)
    return string.format("email:hash:%d:offline:%d", midws.rdb._info.zone, cdkey)
end

---角色邮件列表zsort
local function key_player_zsort(cdkey)
    return string.format("email:zsort:%d:player:%d", midws.rdb._info.zone, cdkey)
end

---角色邮件数据hash
local function key_player_hash(cdkey)
    return string.format("email:hash:%d:player:%d", midws.rdb._info.zone, cdkey)
end

---检查邮件参数
local function checkLarg(larg)
    if not larg.from then
        return false, "from"
    end
    if not larg.toid then
        return false, "toid"
    end
    if not larg.state then
        return false, "state"
    end
    if not larg.title then
        return false, "title"
    end
    if not larg.mtext then
        return false, "mtext"
    end
    return true
end

---@class email:super.basic @邮件对象
local this = class(super)

function this:ctor()
    self:launch()
end

---定时更新自增
function this:hsetGlobalSort()
    local arg = self._arg
    local key = arg.globalKey
    local globalSort = arg.globalSort
    arg.rdb:hset(key, "email:globalSort", globalSort)
end

---生成邮件标识
function this:uuid()
    local arg = self._arg
    local zone = midws.rdb._info.zone
    local sec = timer:nowsec()
    local uuid = (sec * 100000000) + (zone * 1000000) + arg.globalSort
    arg.globalSort = arg.globalSort + 1
    if arg.globalSort >= 100000 then
        arg.globalSort = 1
    end
    local key = "hsetEmailGlobalSort"
    timer:secFirstBy(key, 1, self.hsetGlobalSort, self)
    return uuid
end

function this:launch(carg)
    super.launch(self)
    carg.arg = {
        server = {              ---全服邮件
            hash = {},          ---邮件数据
            list = clist.new(), ---邮件列表
        },                      ---
        player = {              ---玩家邮件
        },                      ---{[cdkey] = {hash = {}}}
        ---待处理邮件
        todo = clist.new(),
        lmt = 99
    }

    ---生成邮件redis全局key
    local zone = midws.rdb._info.zone
    carg.arg.globalKey = string.format("global:hash:%d:data", zone)   ---全服杂项数据
    carg.arg.selistKey = string.format("email:zsort:%d:server", zone) ---全服邮件列表key
    carg.arg.seinfokey = string.format("email:hash:%d:server", zone)  ---全服邮件数据key
    carg.arg.readerKey = string.format("email:hash:%d:reader", zone)  ---玩家已读全服邮件

    ---获取全服邮件自增标识
    local key = carg.arg.globalKey
    midws.rdb:hget(key, "email-server-globalSort")(self.coback, self, arg)
    local sort = self:await()
    carg.arg.globalSort = tonumber(sort) or 1

    ---读取全服邮件列表集合
    key = carg.arg.selistKey
    midws.rdb:zrange(key, 0, -1)(self.coback, self, arg)
    local selist = self:await()
    key = carg.arg.seinfokey

    ---读取全服邮件数据
    local function semailCallback(uuid, str)
        carg.arg.server.hash[uuid] = carg.arg.pb:decode("pbEmail", str)
        carg.arg.server.list:push_back(uuid)
    end
    for _, uuid in ipairs(selist) do
        midws.rdb:hget(key, uuid)(semailCallback, uuid)
    end
    ---等待执行完成
    midws.rdb:await(carg.arg)
    self._arg = carg.arg

    ---小时回调处理
    timer:hourEver(1, function ()
        self:loopHour()
    end)
    ---秒钟回调处理
    timer:secEver(1, function ()
        self:loopSec()
    end)
end

function this:login(carg, cdkey)
    carg.arg = {
        cdkey = cdkey,
        reder = nil,                ---已阅邮件
        temps = {},                 ---临时数据
        offline = {
            dtime = timer:nowsec(), ---离线邮件最大过期时间
            hash = {},              ---邮件数据
            list = clist.new(),     ---邮件列表
        },
        player = {
            dtime = timer:nowsec(), ---在线邮件最大过期时间
            hash = {},              ---邮件数据
            list = clist.new(),     ---邮件列表
        },
    }

    ---加载已阅全服邮件标识
    local key = self._arg.readerKey
    midws.rdb:hget(key, cdkey)(carg.coback, carg)
    local reder = self:await()
    carg.arg.reder = tonumber(reder) or 0

    ---加载角色离线邮件列表
    key = key_offline_zsort(cdkey)
    carg.arg.offlineListKey = key
    carg.arg.rdb:zrange(key, 0, -1)(carg.coback, carg)
    local list = self:await()

    ---加载角色离线邮件数据
    local function offlinecallback(uuid, str)
        uuid = tonumber(uuid)
        local email = carg.arg.pb:decode("pbEmail", str)
        local dtime = carg.arg.offline.dtime
        carg.arg.offline.dtime = math.max(dtime, email.dtime)
        carg.arg.offline.hash[uuid] = email
        carg.arg.offline.list.push_back(uuid)
    end
    key = key_offline_hash(cdkey)
    carg.arg.offlineHashKey = key
    for idx, uuid in ipairs(list) do
        ---加载邮件数据
        carg.arg.rdb:hget(key, uuid)(offlinecallback, uuid)
    end
    midws.rdb:await(carg)

    ---读取角色在线邮件列表
    local key = key_player_zsort(cdkey)
    carg.arg.playerListKey = key
    carg.arg.rdb:zrange(key, 0, -1)(carg.coback, carg)
    list = self:await()

    ---读取角色在线邮件数据
    local function emailCallback(uuid, pack)
        uuid = tonumber(uuid)
        local email = midws.lpb.decode("pbEmail", pack)

        local dtime = carg.arg.player.dtime
        carg.arg.player.dtime = math.max(dtime, email.dtime)
        carg.arg.player.hash[uuid] = email
        carg.arg.player.list.push_back(uuid)
    end
    local key = key_player_hash(cdkey)
    for _, uuid in ipairs(list) do
        carg.arg.rdb:hget(key, uuid)(emailCallback, uuid)
    end

    ---等待执行完成
    midws.rdb:await(carg)
    ---邮件登录完成
    self._arg.player[carg.arg.cdkey] = carg.arg
    ---角色定时回调
    carg.arg.secTimerId = timer:secEver(3, function ()
        self:playerTick(carg.arg)
    end)
    carg.arg = nil
end

---小时定时回调
function this:loopHour()
    local arg = self._arg
    ---定时检查删除全服邮件
    local olist = arg.server.list
    local nowsec = timer:nowsec()

    local dels = tvoid()
    repeat
        ---没有邮件
        if olist:size() <= 0 then
            break
        end

        ---检查过期时间
        local node = olist:reder()
        local uuid = node.data
        local email = arg.server.hash[uuid]
        if email.dtime > nowsec then
            break
        end

        ---删除内存数据
        arg.server.hash[uuid] = nil
        olist:pop_front()
        table.insert(dels, uuid)

        ---检查20删除上限
        if #dels >= 20 then
            break
        end
    until false

    if next(dels) then
        ---删除列表
        local key = arg.selistKey
        midws.rdb:zrem(key, table.unpack(dels))
        ---删除数据
        key = arg.seinfokey
        midws.rdb:hdel(key, table.unpack(dels))
    end
end

---每秒定时回调
function this:loopSec()
    local arg = self._arg
    ---定时检查待处理邮件
    repeat
        if arg.todo:size() <= 0 then
            break
        end

        ---遍历邮件处理
        while arg.todo:reder() do
            local email = arg.todo:pop_front()

            local toid = email.toid
            if not cdkeys[toid] then
                ---玩家不在线
            else
                local player = arg.player[toid]
                if player then
                    ---数据已加载完成
                    local offline = player.offline
                    if not offline.hash[email.uuid] then
                        offline.hash[email.uuid] = email
                        offline.list.push_back(email.uuid)
                    end
                end
            end
        end
    until false

    ---定时读取邮件
    midws.xdb:xgetMsg()(self.xgetMsg, self)
end

function this:xgetMsg(list, ids)
    local arg = self._arg
    for _, msg in ipairs(list) do
        local email = self:genEmail(msg)
        arg.todo:push_back(email)
    end
    ---消息处理回应
    midws.xdb:xackMsg(ids)
end

function this:playerTick(arg)
    ---不在线删除
    if not cdkeys[arg.cdkey] then
        return "delete"
    end

    local garg = self._arg
    local limit = garg.limit
    local rdb = midws.rdb

    local gnode = garg.server.list:tail()
    local reder = arg.reder
    repeat
        if not gnode then
            break ---没有全服邮件
        end

        local guid = gnode.data
        if reder >= guid then
            break ---已经检查过了
        end

        if not gnode.front then
            break ---前面已经没了
        end

        gnode = gnode.front
    until false

    ---放入离线邮件
    local redEmail = clear(arg.temps)
    while gnode do
        local guid = gnode.data
        if reder < guid then
            reder = guid ---更新审阅
            local email = garg.server.hash[guid]
            ---添加离线邮件
            local nemail = clone(email)
            ---更新邮件标识(保证每个邮件唯一)
            nemail.uuid = self:uuid()
            nemail.toid = arg.cdkey
            arg.offline.list:push(guid)
            arg.offline.hash[guid] = nemail
            redEmail[guid] = nemail
        end
        gnode = gnode.later
    end

    ---更新已阅状态
    if arg.reder ~= reder then
        arg.reder = reder
        rdb:hset(garg.emailhashReaderKey, arg.cdkey, reder)
    end

    ---处理离线邮件
    local moves = tvoid()
    repeat
        if arg.player.list:size() >= limit then
            break ---当前邮件太多
        end

        if arg.offline.list:size() <= 0 then
            break ---没有离线邮件
        end

        local node = arg.offline.list:pop_front()
        local uuid = node.data
        local email = arg.offline.hash[uuid]
        ---清理离线内存数据
        arg.offline.hash[uuid] = nil
        ---保存放入在线邮件
        arg.player.hash[uuid] = email
        arg.player.list:push_back(email)
        ---记录添加邮件数据
        table.insert(moves, uuid)
        ---删除写入离线数据
        redEmail[uuid] = nil
    until false

    ---邮件数据入库处理
    if next(moves) then
        ---删除离线邮件列表
        local key = arg.offlineListKey
        rdb:zrem(key, table.unpack(moves))
        ---删除离线邮件数据
        key = arg.offlineHashKey
        rdb:hdel(arg.arg.offlineHashKey, table.unpack(moves))
        ---添加在线邮件列表
        key = arg.playerListKey
        rdb:zadd(arg.playerListKey, rdb:list2zadd(moves))
        ---添加在线邮件数据
        local dtime = 0
        for _, uuid in ipairs(moves) do
            arg.uuid = uuid
            key = key_player_hash(arg)
            local email = arg.player.hash[uuid]
            rdb:hmset(key, rdb:unhashpack(email))
            ---在线邮件数据过期更新
            rdb:evalsha("expireatMax", 2, key, dtime)
        end

        ---在线邮件列表过期更新
        key = arg.playerListKey
        rdb:evalsha("expireatMax", 2, key, dtime)
    end

    ---离线邮件-数据入库
    local pb = garg.pb
    if next(redEmail) then
        ---添加离线邮件列表
        local key = arg.offlineListKey
        rdb:zadd(arg.offlineListKey, rdb:set2zadd(redEmail))
        ---添加离线邮件数据
        local dtime = 0
        for uuid, email in ipairs(redEmail) do
            arg.uuid = uuid
            key = arg.offlineHashKey
            local pack = pb:encode("pbEmail", email)
            rdb:hset(key, uuid, pack)
            dtime = math.max(dtime, email.dtime)
        end

        ---离线列表过期更新
        key = arg.offlineListKey
        rdb:evalsha("expireatMax", 2, key, dtime)
        ---离线数据过期更新
        rdb:evalsha("expireatMax", 2, key, dtime)
    end
end

---生成邮件数据
function this:genEmail(larg)
    local now = timer:nowsec()
    return {
        from = larg.from or 0,
        toid = larg.toid or 0,
        uuid = self:uuid(),
        minlv = larg.minlv or 0,
        maxlv = larg.maxlv or 0,
        ctime = now,
        dtime = now + (86400 * 30),
        state = eflgs.init,
        title = larg.title,
        mtext = larg.mtext,
        prize = larg.prize,
    }
end

---生成角色邮件
function this:roleEmail(larg)
    ---检查邮件必要参数
    local ok, key = checkLarg(larg)
    if not ok then
        assert(false, string.format("email args not:%s", key))
    end

    ---检查邮件是否初始
    local garg = self._arg
    if not garg then
        assert(false, "email not init")
    end

    local email = this:genEmail(larg)
    ---写入离线邮件列表
    local key = key_offline_zsort(larg.toid)
    midws.rdb:zadd(key, email.uuid, email.uuid)
    ---离线邮件列表-过期时间
    midws.rdb:evalsha("expireatMax", 2, key, email.dtime)
    ---写入离线邮件数据
    key = key_offline_hash(email.toid)
    local pack = midws.lpb:encode("pbEmail", email)
    midws.rdb:hset(key, email.uuid, pack)
    ---离线邮件数据-过期时间
    midws.rdb:evalsha("expireatMax", 2, key, email.dtime)
end

---生成全服邮件
function this:zoneEmail(larg)
    larg.toid = 0
    local ok, key = checkLarg(larg)
    if not ok then
        assert(false, string.format("email args not:%s", key))
    end
    local arg = self._arg
    if not arg then
        assert(false, "email not init")
    end

    ---生成邮件唯一标识
    larg.uuid = self:uuid()

    ---保存全服邮件列表
    local key = arg.selistKey
    midws.rdb:zadd(key, arg.uuid, arg.uuid)
    midws.rdb:evalsha("expireatMax", 2, key, larg.dtime)
    ---保存全服邮件数据
    key = arg.seinfokey
    local pack = arg.pb:encode("pbEmail", larg)
    midws.rdb:hset(key, arg.uuid, pack)
    midws.rdb:evalsha("expireatMax", 2, key, larg.dtime)
    ---数据保存到内存
    arg.server.hash[arg.uuid] = larg
    arg.server.list.push_back(arg.uuid)
end

return this

--[[

    global:hash:zone:data = {
        ["email:globalSort"] = 自增标识
    }

    服务状态 已阅(唯一)
    email:hash:zone:reader = {
        [cdkey] = 玩家已阅
    }

    全服邮件 列表(唯一)
    email:zsort:zone:server = {
        [uuid] = uuid,---邮件数据
    }

    全服邮件 数据(唯一)
    email:hash:zone:server = {
        [uuid] = pbEmail,---邮件数据
    }

    离线邮件 列表(角色)
    email:zsort:zone:offline:cdkey = {
        [uuid] = uuid,---邮件标识
    }

    离线邮件 数据(角色)
    email:hash:zone:offline:cdkey = {
        [uuid] = pbEmail,---邮件数据
    }

    玩家邮件 列表(角色)
    email:zsort:zone:player:cdkey = {
        [uuid] = uuid,---邮件标识
    }

    玩家邮件 数据(角色)(不直接用二进制是因为 statue 需要更新)
    email:hash:zone:player = {
        [uuid] = {  pbEmail
            uuid   = 邮件标识 sec*1亿 + zoineid * 百万 + (1~10万-1)
            from   = 发送用户 0:[系统|活动|补偿|公告]
            toid   = 接收用户 0:全区
            minlv  = 最低等级
            maxlv  = 最高等级
            ctime  = 创建时间
            dtime  = 过期时间
            state  = 邮件状态
            title  = 邮件标题
            mtext  = 邮件内容
            prize  = 邮件附件-proto
        }
    }
]]
